package com.sh.springframework;

import com.sh.springframework.context.ClassBean;
import com.sh.springframework.context.PropBean;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;

import java.io.InputStream;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 * @Auther: sh
 * @Version 1.0
 * @Description: 自定义Spring核心容器，实现基于XML配置文件的IoC功能
 */
public class ApplicationContext {

    // XML配置文件路径
    private String xmlFile;

    // 存放解析后的Bean定义信息（ClassBean），key为Bean名称
    private Map<String, ClassBean> beanInfoMap = new HashMap<>();

    // 存放已实例化的Bean对象，key为Bean名称
    private Map<String, Object> beanContaionerMap = new HashMap<>();


    /**
     * 构造方法，初始化容器，依次完成：
     * 1. 解析XML文件，加载Bean定义
     * 2. 实例化所有Bean对象
     * 3. 给Bean对象注入属性，组装依赖关系
     * @param xmlFile 配置文件路径，类路径下的文件，例如 "/spring.xml"
     */
    public ApplicationContext(String xmlFile) {
        this.xmlFile = xmlFile;
        parseXml();      // 解析xml文件，加载bean定义信息
        newBean();       // 实例化bean对象
        propToBean();    // 注入属性，组装依赖关系
    }


    /**
     * 解析XML配置文件，加载Bean的定义信息到beanInfoMap中
     * 使用dom4j读取XML，解析每个<bean>标签及其<property>标签
     */
    public void parseXml() {
        System.out.println("解析xml文件");
        SAXReader saxReader = new SAXReader();
        // 通过类加载器读取xml文件
        InputStream ins = this.getClass().getResourceAsStream(xmlFile);
        try {
            Document document = saxReader.read(ins);
            Element rootElement = document.getRootElement();  // <beans>根元素
            List<Element> beans = rootElement.elements("bean");  // 获取所有<bean>节点
            for (Element bean : beans) {
                String name = bean.attributeValue("name");     // Bean名称
                String zClass = bean.attributeValue("class");  // Bean对应的类全名

                ClassBean classBean = new ClassBean();
                classBean.setName(name);
                classBean.setClsName(zClass);

                // 解析当前bean的所有属性
                List<Element> propertys = bean.elements("property");
                Map<String, PropBean> propBeans = new HashMap<>();
                for (Element property : propertys) {
                    String propertyName = property.attributeValue("name");  // 属性名
                    String propertyValue = property.attributeValue("value"); // 属性值（简单类型）
                    String propertyRef = property.attributeValue("ref");     // 引用其他Bean

                    PropBean propBean = new PropBean();
                    propBean.setName(propertyName);
                    propBean.setValue(propertyValue);
                    propBean.setRef(propertyRef);

                    propBeans.put(propertyName, propBean);
                }
                classBean.setPropBean(propBeans);

                // 将解析好的Bean定义信息保存到map
                beanInfoMap.put(name, classBean);
            }
        } catch (DocumentException e) {
            throw new RuntimeException("解析xml文件失败", e);
        }
    }


    /**
     * 根据beanInfoMap中的Bean定义，使用反射实例化所有Bean对象，
     * 并存放到beanContaionerMap中，完成Bean的创建（单例）
     */
    public void newBean() {
        Set<Map.Entry<String, ClassBean>> entries = beanInfoMap.entrySet();
        for (Map.Entry<String, ClassBean> entry : entries) {
            ClassBean classBean = entry.getValue();
            String name = classBean.getName();
            String clsName = classBean.getClsName();

            try {
                // 反射实例化Bean对象，调用无参构造函数
                Object beanObj = Class.forName(clsName).getDeclaredConstructor().newInstance();

                // 将Bean对象放入容器
                beanContaionerMap.put(name, beanObj);
            } catch (Exception e) {
                throw new RuntimeException("实例化Bean失败，class:" + clsName, e);
            }
        }
    }


    /**
     * 属性注入
     * 根据beanInfoMap中的属性定义，给实例化的Bean对象通过反射调用setter方法注入属性值
     * 支持：
     * 1. 简单类型属性注入（int,float,double,boolean,String）
     * 2. Bean引用注入（ref）
     */
    public void propToBean() {
        Set<Map.Entry<String, ClassBean>> entries = beanInfoMap.entrySet();

        for (Map.Entry<String, ClassBean> entry : entries) {
            ClassBean classBean = entry.getValue();
            Map<String, PropBean> propBeans = classBean.getPropBean();
            Set<Map.Entry<String, PropBean>> propSet = propBeans.entrySet();

            // 获取当前Bean实例对象
            Object beanObj = getBean(classBean.getName());

            for (Map.Entry<String, PropBean> prop : propSet) {
                PropBean propBean = prop.getValue();

                String name = propBean.getName();   // 属性名
                String value = propBean.getValue(); // 属性值
                String ref = propBean.getRef();     // 引用Bean名称

                try {
                    // 获取属性对应的Field
                    Field declaredField = beanObj.getClass().getDeclaredField(name);
                    Class<?> type = declaredField.getType();  // 属性类型

                    // 构造setter方法名，如setName
                    String methodName = "set" + name.substring(0, 1).toUpperCase() + name.substring(1);

                    // 获取setter方法
                    Method declaredMethod = beanObj.getClass().getDeclaredMethod(methodName, type);

                    if (ref != null && !"".equals(ref)) {
                        // 如果ref属性不为空，注入引用的Bean对象
//                        Object bean = getBean(ref);
//                        declaredMethod.invoke(beanObj, bean);
                        declaredMethod.invoke(beanObj, getBean(ref));
                    } else {
                        // 处理基本类型的转换
                        Object vv = value;
                        if (type == int.class || type == Integer.class) {
                            vv = Integer.parseInt(value);
                        } else if (type == float.class || type == Float.class) {
                            vv = Float.parseFloat(value);
                        } else if (type == double.class || type == Double.class) {
                            vv = Double.parseDouble(value);
                        } else if (type == boolean.class || type == Boolean.class) {
                            vv = Boolean.parseBoolean(value);
                        }
                        // 调用setter注入属性值
                        declaredMethod.invoke(beanObj, vv);
                    }
                } catch (Exception e) {
                    throw new RuntimeException("注入属性失败，Bean：" + classBean.getName() + " 属性：" + name, e);
                }
            }
        }
    }


    /**
     * 根据Bean名称获取实例化后的Bean对象
     * @param beanName Bean名称
     * @param <T> 返回的Bean类型
     * @return 实例化后的Bean对象
     */
    public <T> T getBean(String beanName) {
        return (T) beanContaionerMap.get(beanName);
    }

}
